using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace XcoAppSpaces.Core.Ports
{
	/// <summary>
	/// Interface that all port and portset classes implement.
	/// </summary>
	public interface IPort
	{
		/// <summary>
		/// The type of messages that can be posted to the port. For a portset, this property throws an exception.
		/// </summary>
		Type MessageType { get; }

		/// <summary>
		/// Posts an item to the port. If the message doesn't correspond to the port's type, an exception is thrown.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		void PostUnknownType(object item);

		/// <summary>
		/// Posts an item to the port. If the message doesn't correspond to the port's type, an exception is thrown.
		/// The given error port is used as causality, when exceptions are thrown.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		/// <param name="errorPort">The error port to be used when exceptions are thrown.</param>
		void PostUnknownType(object item, IPort<Exception> errorPort);

		/// <summary>
		/// Posts an item to the port. If the message doesn't correspond to the port's type, an exception is thrown.
		/// The given error and ack ports are used as causality.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		/// <param name="errorPort">The error port to be used when exceptions are thrown.</param>
		/// <param name="ackPort">The port where an ack message should be posted.</param>
		void PostUnknownType(object item, IPort<Exception> errorPort, IPort<Ack> ackPort);
		
		/// <summary>
		/// Posts an item to the port and returns a task that completes as soon as the item was sent (or goes
		/// into faulted state when sending fails). If the port is not remote, then a task will be returned
		/// that is already completed. If the message doesn't correspond to the port's type, an exception is thrown.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		/// <returns>A task to wait for the item to be sent.</returns>
		Task PostUnknownTypeAndAwaitSent(object item);

		/// <summary>
		/// Posts an item to the port and returns a task that completes as soon as the item was sent (or goes
		/// into faulted state when sending fails). If the port is not remote, then a task will be returned
		/// that is already completed. If the message doesn't correspond to the port's type, an exception is thrown.
		/// Since communication errors are returned to the task, the given error port does not react to
		/// communication errors, but only to errors happening later when the message is being processed.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		/// <param name="errorPort">Error port for receiving exceptions that happen during message processing.</param>
		/// <returns>A task to wait for the item to be sent.</returns>
		Task PostUnknownTypeAndAwaitSent(object item, IPort<Exception> errorPort);
		
		
		/// <summary>
		/// Registers a receiver which should be used for processing items that are posted to the port.
		/// An exception is thrown if the delegate is not usable for the port's message type.
		/// </summary>
		/// <param name="receiver">The receiver delegate.</param>
		void RegisterUnknownTypeReceiver(Delegate receiver);

		/// <summary>
		/// Registers a receiver which should be used for processing items that are posted to the port.
		/// An exception is thrown if the delegate is not usable for the port's message type.
		/// </summary>
		/// <param name="receiver">The receiver delegate.</param>
		/// <param name="options">Processing options, like MaxDegreeOfParallelism.</param>
		void RegisterUnknownTypeReceiver(Delegate receiver, PortReceiverOptions options);
		
		/// <summary>
		/// Unregisters the currently installed receiver.
		/// </summary>
		void UnregisterReceiver();
	}

	/// <summary>
	/// Interface that is implemented by the port class.
	/// </summary>
	/// <typeparam name="T">The message type of the port.</typeparam>
	public interface IPort<T> : IPort
	{
		/// <summary>
		/// The number of items that are currently in the port.
		/// </summary>
		int ItemCount { get; }

		/// <summary>
		/// Posts an item to the port.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		void Post(T item);

		/// <summary>
		/// Posts an item to the port. The given error port is used as causality, when exceptions are thrown.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		/// <param name="errorPort">The error port to be used when exceptions are thrown.</param>
		void Post(T item, IPort<Exception> errorPort);
		
		/// <summary>
		/// Posts an item to the port. The given error and ack ports are used as causality.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		/// <param name="errorPort">The error port to be used when exceptions are thrown.</param>
		/// <param name="ackPort">The port where an ack message should be posted.</param>
		void Post(T item, IPort<Exception> errorPort, IPort<Ack> ackPort);


		/// <summary>
		/// Posts an item to the port and returns a task that completes as soon as the item was sent (or goes
		/// into faulted state when sending fails). If the port is not remote, then a task will be returned
		/// that is already completed.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		/// <returns>A task to wait for the item to be sent.</returns>
		Task PostAndAwaitSent(T item);

		/// <summary>
		/// Posts an item to the port and returns a task that completes as soon as the item was sent (or goes
		/// into faulted state when sending fails). If the port is not remote, then a task will be returned
		/// that is already completed. If the message doesn't correspond to the port's type, an exception is thrown.
		/// Since communication errors are returned to the task, the given error port does not react to
		/// communication errors, but only to errors happening later when the message is being processed.
		/// </summary>
		/// <param name="item">The item to be posted.</param>
		/// <param name="errorPort">Error port for receiving exceptions that happen during message processing.</param>
		/// <returns>A task to wait for the item to be sent.</returns>
		Task PostAndAwaitSent(T item, IPort<Exception> errorPort);

		/// <summary>
		/// Asynchronously receives an item from the port, with infinite timeout.
		/// </summary>
		/// <returns>A task for asynchronously receiving an item from the port.</returns>
		Task<T> Receive();

		/// <summary>
		/// Asynchronously receives an item from the port, with the specified timeout.
		/// </summary>
		/// <param name="timeout">The maximum time in milliseconds to wait for an item.</param>
		/// <returns>A task for asynchronously receiving an item from the port.</returns>
		Task<T> Receive(int timeout);

		/// <summary>
		/// Removes all items from the port.
		/// </summary>
		void Clear();

		/// <summary>
		/// Registers a receiver which should be used for processing items that are posted to the port.
		/// </summary>
		/// <param name="receiver">The receiver action.</param>
		void RegisterReceiver(Action<T> receiver);

		/// <summary>
		/// Registers a receiver which should be used for processing items that are posted to the port.
		/// </summary>
		/// <param name="receiver">The receiver action.</param>
		/// <param name="options">Processing options, like MaxDegreeOfParallelism.</param>
		void RegisterReceiver(Action<T> receiver, PortReceiverOptions options);

		/// <summary>
		/// Registers a receiver which should be used for processing items that are posted to the port.
		/// This kind of receiver allows the use of async/await.
		/// </summary>
		/// <param name="receiver">The receiver function.</param>
		void RegisterReceiver(Func<T, Task> receiver);

		/// <summary>
		/// Registers a receiver which should be used for processing items that are posted to the port.
		/// This kind of receiver allows the use of async/await.
		/// </summary>
		/// <param name="receiver">The receiver function.</param>
		/// <param name="options">Processing options, like MaxDegreeOfParallelism.</param>
		void RegisterReceiver(Func<T, Task> receiver, PortReceiverOptions options);
	}
}